package binance

import (
	"global/libs/structs"
	"srv/kline_1min/exchanges"
	"fmt"
	"strings"
	"global/model"
	"encoding/json"
	"github.com/ngaut/log"
	"time"
	"strconv"
	"srv/kline/exchange"
	"github.com/shopspring/decimal"
)

type BinanceKlineHandle struct {
	base
	exchange 			string
	table 				string
	symbol 				*structs.Symbol
	symbolStr 			string

	// 去重复数据
	repeats 			map[string]struct{}

	max 				int
	last 				model.Kline 		// 最后一个

	tickerC 			*time.Ticker
}

func NewBinanceKlineHandle(exchange string, symbol *structs.Symbol) exchanges.KlineInterface {
	return &BinanceKlineHandle{
		exchange: 		exchange,
		table: 			fmt.Sprintf("kline_%s", strings.ToLower(symbol.From)),
		symbol: 		symbol,
		symbolStr: 		fmt.Sprintf("%s_%s", strings.ToLower(symbol.From), strings.ToLower(symbol.To)),
		repeats: 		make(map[string]struct{}),
		tickerC: 		time.NewTicker(1 * time.Minute),
	}
}

// 是否需要获取历史数据
func (h *BinanceKlineHandle) NeedGetHistoryData() (bool, error) {
	has, err := model.KlineModel.HasItem(h.table, h.exchange, h.symbol)
	if err != nil {
		return false, err
	}
	return !has, nil
}

func (h *BinanceKlineHandle) History() {
	var (
		to string
		err error
		data []*model.Kline
	)
	// 获取最小时间，历史最高价，历史最低价

	for {
		data, to, err = h.request(to)
		if err != nil {
			log.Errorf("Error: %s", err)
			continue
		}

		// 去重复
		var storeDatas []*model.Kline
		for _, v := range data {
			if _, ok := h.repeats[v.Time]; !ok {
				storeDatas = append(storeDatas, v)
				h.repeats[v.Time] = struct{}{}
			}
		}

		if len(storeDatas) == 0 {
			break
		}

		// save
		h.save(storeDatas)
	}

	h.repeats = nil
}

func (h *BinanceKlineHandle) Latest() {
	max, err := model.KlineModel.GetMax(h.table, h.exchange, h.symbol)
	if err != nil {
		log.Errorf("get max time failed, err: %s", err)
		return
	}
	log.Infof("get max item, exchange:%s, symbol:%s, max:%d",
		h.exchange, h.symbolStr, max)
	if max == 0 {
		return
	}

	h.max = max

	for t := range h.tickerC.C {
		_ = t
		datas, _, err := h.request("")
		if err != nil {
			log.Errorf("request error: %s", err)
			continue
		}

		var latestMax int
		var storeDatas []*model.Kline
		for _, v := range datas {

			newTime, _ := strconv.Atoi(v.Time)
			if latestMax < newTime {
				latestMax = newTime
			}

			if newTime == h.max {
				// 修改
				log.Infof("update item: exchange:%s, symbol: %s, time:%s",
					h.exchange, h.symbolStr, v.Time)
				model.KlineModel.Update(h.table, v)
			} else if newTime > h.max {
				// 插入
				storeDatas = append(storeDatas, v)
			}
		}

		log.Infof("insert items, exchange: %s, symbol:%s, length: %d",
			h.exchange, h.symbolStr, len(storeDatas))
		if len(storeDatas) > 0 {
			h.save(storeDatas)
		}

		h.max = latestMax
	}
}

func (h *BinanceKlineHandle) save(datas []*model.Kline)  {
	_, err := model.KlineModel.InsertMulti(h.table, datas)
	if err != nil {
		// 如果出错就逐条插入
		for _, v := range datas {
			if _, err := model.KlineModel.Insert(h.table, v); err != nil {
				log.Errorf("Error: %s, v.time: %s", err, v.Time)
			}
		}
	}

}

// 请求数据
func (h *BinanceKlineHandle) request(to string) ([]*model.Kline, string, error) {

	url := h.url("klines")
	params := map[string]interface{}{
		"symbol": strings.ToUpper(fmt.Sprintf("%s%s", strings.ToUpper(h.symbol.From), strings.ToUpper(h.symbol.To))),
		"interval": exchange.BinanceKlineResolution1min,
		"limit": 1000,
	}

	if to != "" {
		params["endTime"] = to
	}

	body, err := h.get(url, params)

	if err != nil {
		return nil, to, err
	}

	response := make([][]interface{}, 0)
	if err := json.Unmarshal([]byte(body), &response); err != nil {

		type errorResp struct {
			Code 		int 			`json:"code"`
			Msg 		string 			`json:"msg"`
		}

		var e errorResp
		if err = json.Unmarshal([]byte(body), &e); err == nil {
			return nil, to, fmt.Errorf("code: %d, msg: %s, params: %+v", e.Code, e.Msg, params)
		}

		return nil, to, err
	}

	var sTime string
	datas := make([]*model.Kline, 0)
	for _, resp := range response {
		if len(resp) < 12 { continue }

		dateTime, _ := resp[0].(float64)
		open, _ := resp[1].(string)
		high, _ := resp[2].(string)
		low, _ := resp[3].(string)
		price, _ := resp[4].(string)
		vol, _ := resp[5].(string)

		d := decimal.NewFromFloat(dateTime)
		fm := decimal.NewFromFloat(1000)

		datas = append(datas, &model.Kline{
			Exchange: 		"binance",
			Symbol: 		h.symbolStr,
			CoinBase: 		h.symbol.From,
			CoinQuote: 		h.symbol.To,
			Time: 			d.String(),
			Open: 			open,
			High: 			high,
			Low: 			low,
			Close: 			price,
			Volume: 		vol,
			Datetime: 		time.Unix(d.Div(fm).IntPart(), 0).Format("2006-01-02 15:04:05"),
		})
	}

	if len(response) > 0 {
		d, _ := response[0][0].(float64)
		sTime = decimal.NewFromFloat(d).String()
	}

	return datas, sTime, nil
}
